    
# global imports
from OFS.Folder import Folder
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
import os.path
import Globals
import AccessControl
import permissions
from time import strftime
from DateTime import DateTime

# package imports
import interfaces, utils

### global constants
# status and resolutions
STATUS_OPEN = "OPEN"
STATUS_CLOSED = "CLOSED"
ALL_STATUS_ITEMS = [ ("NEW", "New"),
                     ("ANALISYS", "Analisys"),
                     ("CLARIFY", "Clarify"),
                     ("RE-SENT", "Re-Sent"),
                     ("FIXING", "Fixing"),
                     ("TESTING", "Testing"),
                     ("FIXED", "Fixed"),
                     ("APPROVED", "Approved"),
                     ("DISCARDED", "Discarded"),
                     ("NON-BUG", "Non-Bug"),
                     ("POSTPONED", "Postponed") ]

# status symbols
ALL_STATUS = []
# map from symbols to (english) Text
ALL_STATUS_TEXT = {}
for name, value in ALL_STATUS_ITEMS:
    ALL_STATUS.append(name)
    ALL_STATUS_TEXT[name] = value

status_transitions = { "NEW"      :["NEW"      ,"ANALISYS"],
                       "ANALISYS" :["ANALISYS" ,"FIXING","CLARIFY",
                                    "DISCARDED","NON-BUG","POSTPONED"],
                       "CLARIFY"  :["CLARIFY"  ,"RE-SENT"],
                       "RE-SENT"  :["RE-SENT"  ,"ANALISYS"],
                       "FIXING"   :["FIXING"   ,"FIXED","TESTING"],
                       "TESTING"  :["TESTING"  ,"FIXING","FIXED"],
                       "FIXED"    :["FIXED"    ,"APPROVED","RE-SENT"],
                       "APPROVED" :["APPROVED"  ],
                       "DISCARDED":["DISCARDED"],
                       "NON-BUG"  :["NON-BUG"  ],
                       "POSTPONED":["POSTPONED", "RE-SENT"] }



ALL_STATUS_RESULTS = {}
# assemble ALL_STATUS_RESULTS,
# deciding the resolution of each status
iopen = ALL_STATUS.index("APPROVED")
for i in range(iopen):
    ALL_STATUS_RESULTS[ALL_STATUS[i]] = STATUS_OPEN
for i in range(iopen, len(ALL_STATUS)):
    ALL_STATUS_RESULTS[ALL_STATUS[i]] = STATUS_CLOSED

# possible bug priorities, strings representing numbers
# this is ok because we don't do arithmetic
ALL_PRIORITIES = map(str, range(1,6))

# severity options
ALL_SEVERITIES_ITEMS = [ ('CRITICAL', 'Critical'),
                         ('UNAVOIDABLE', 'Unavoidable'),
                         ('AVOIDABLE', 'Avoidable'),
                         ('COSMETIC', 'Cosmetic'),
                         ('IMPROVEMENT', 'Improvement'),
                        ]
# severity symbols
ALL_SEVERITIES = []
# map from symbols to (english) texts
ALL_SEVERITIES_TEXT = {}
# let's assemble them
for name, value in ALL_SEVERITIES_ITEMS:
    ALL_SEVERITIES.append(name)
    ALL_SEVERITIES_TEXT[name] = value

_wwwDir = os.path.join(os.path.dirname(__file__), 'www')

class Bug(Folder):
    # security object to make calls ans objects secure
    security = AccessControl.ClassSecurityInfo()
    security.declareObjectProtected(AccessControl.Permissions.access_contents_information)
    security.setPermissionDefault(permissions.EDIT_BUG, ('Manager', 'Customer'))
    
    __implements__ = interfaces.IBug

    ### Zope standard attributes
    meta_type = "DDBug Bug"
    
    properties = {'reported_by': {'type':'string', 'mode':'w', 'editable_by':''},
                  'summary': {'type':'text', 'mode':'w', 'editable_by':'CP'},
                  'status': {'type':'selection', 'mode':'w','select_variable':'optionsForStatus', 'editable_by':'CP'},
                  'priority': {'type':'selection', 'mode':'w','select_variable':'optionsForPriority', 'editable_by':'C'},
                  'severity': {'type':'selection', 'mode':'w','select_variable':'optionsForSeverity', 'editable_by':'P'},
                  'error_message': {'type':'text', 'mode':'w', 'editable_by':'CP'},
                  'url': {'type':'string', 'mode':'w', 'editable_by':'CP'},
                  'browser': {'type':'string', 'mode':'w', 'editable_by':'CP'},
                  'steps_to_reproduce': {'type':'text', 'mode':'w', 'editable_by':'CP'},
                  'comments': {'type':'text', 'mode':'w', 'editable_by':'CP'},
                  'assigned_to': {'type':'string', 'mode':'w', 'editable_by':''},
                  'workaround': {'type':'text', 'mode':'w', 'editable_by':'P'},
                  'estimated_time_to_fix': {'type':'int', 'mode':'w', 'editable_by':'P'},
                  'actual_time_to_fix': {'type':'int', 'mode':'w', 'editable_by':'P'},
                  'log': {'type':'lines', 'mode':'w', 'editable_by':''},
                  'modification_time': {'type':'date', 'mode':'w', 'editable_by':''},
    }

    # food for PropertyManager 
    _properties = []
    for key, value in properties.items():
        property = {}
        property['id'] = key
        dict = value.copy()
        del dict['editable_by'] # remove field not used by PropertyManager
        property.update(dict)
        _properties.append(property)
    
    _properties = tuple(_properties)

    # and now, the defaults of the above properties
    reported_by = ''
    summary = ''
    status = '' # it will be set in __init__
    priority = ALL_PRIORITIES[-1]
    severity = ALL_SEVERITIES[-1]
    error_message = ''
    url = ''
    browser = ''
    steps_to_reproduce = ''
    comments = ''
    assigned_to = ''
    workaround = ''
    estimated_time_to_fix = 0
    actual_time_to_fix = 0
    log = None
    modification_time = DateTime("1970-01-01")

    ALL_STATUS = ALL_STATUS    

    security.declareProtected(AccessControl.Permissions.access_contents_information, 'title')
    def title(self):
        return self.summary[:200]

    def __init__(self, id, priority=priority):
        self.id = id
        self.priority = priority
        self.status = self.initialStatus()
        self.log = []
        self.modification_time = DateTime()

    security.declareProtected(AccessControl.Permissions.view, 'initialStatus')
    def initialStatus(self):
        return ALL_STATUS[0]

    security.declareProtected(AccessControl.Permissions.view, 'optionsForStatus')
    def optionsForStatus(self):
        return ALL_STATUS

    security.declareProtected(AccessControl.Permissions.view, 'optionsForPriority')
    def optionsForPriority(self):
        return ALL_PRIORITIES

    security.declareProtected(AccessControl.Permissions.view, 'optionsForSeverity')
    def optionsForSeverity(self):
        return ALL_SEVERITIES


    # pull in the i18n machinery
    security.declarePrivate("xlate")
    _xlate=utils.xlate

    security.declareProtected(AccessControl.Permissions.view,
                              'getStatusLabel')
    def getStatusLabel(self, status=None):
        """Returns a label for the status (potentially translated)
        If not provided, status defaults to the current bug status"""
        if not status:
            status = self.status
        return self._xlate(msgid=status, text=ALL_STATUS_TEXT[status])

    security.declareProtected(AccessControl.Permissions.view,
                              'getSeverityLabel')
    def getSeverityLabel(self, severity=None):
        """Returns a label for the severity (potentially translated)
        If not provided severity defaults to the current bug severity"""
        if not severity:
            severity = self.severity
        return self._xlate(msgid=severity, text=ALL_SEVERITIES_TEXT[severity])

    security.declareProtected(AccessControl.Permissions.view,
                              'getPriorityLabel')
    def getPriorityLabel(self, priority=None):
        """Return the priority passed in or the current bug priority"""
        if not priority:
            priority = self.priority
        # priority is just a (string repr. of a) number
        # no need to translate it
        return priority

    security.declareProtected(AccessControl.Permissions.view, 'detectBrowser')
    def detectBrowser(self):
        """ The world's simplest browser detection method.
            Some day we will have a consolidated list of browser types,
            and this method will match the HTTP_USER_AGENT with one
            of them.
        """
        return self.REQUEST['HTTP_USER_AGENT']

    security.declareProtected(permissions.EDIT_BUG, 'manage_editBug')
    def manage_editBug(self, REQUEST=None, **kw):
        """Bug editing method
        Changes the bug's properties
        """
        
        self.modification_time = DateTime()
        if REQUEST is None:
            props={}
        else: props=REQUEST
        for name, value in props.items():
            if self.hasProperty(name) or name == 'attachment':
                kw[name]=value
        
        date = strftime("%Y/%m/%d %H:%M")
        #log
        transition = ''
        if kw.has_key('status') and self.status != kw['status']:
            transition = "%s Status altered from %s to %s by %s" % (
                         date,
                         self.getStatusLabel(),
                         self.getStatusLabel(kw['status']),
                         self.getAuthenticatedUser())
        elif not self.log:
            transition = "%s Bug created by %s" % (date, self.getAuthenticatedUser())
        if transition:
            log = self.log
            log.append(transition)
            kw['log'] = log
        #comments
        #the comments are joined with the || mark
        if kw.has_key('comments') and kw['comments'] != '':
            if self.comments:
                kw['comments'] = '%s||%s %s by %s' % (
                        self.comments,
                        date,                        
                        kw['comments'],
                        self.getAuthenticatedUser())
            else:
                kw['comments'] = '%s %s by %s' % (
                        date,
                        kw['comments'],
                        self.getAuthenticatedUser())
        else:
            kw['comments'] = self.comments

        #assigned_to
        user = self.getAuthenticatedUser()
        roles = user.getRolesInContext(self)
        if 'Manager' in roles:
            kw['assigned_to'] = str(user)
        elif kw.has_key('assigned_to'):
            del kw['assigned_to']
        self.manage_changeProperties(**kw)
        if kw.has_key('attachment') and kw['attachment']:
            self.manage_addFile(id='',title='', file=kw['attachment'])
        if REQUEST:
            self.REQUEST.RESPONSE.redirect(self.aq_parent.absolute_url())

    security.declareProtected(AccessControl.Permissions.view, 'getTransitionOptions')
    def getTransitionOptions(self):
        """getTransitionOptions() -> list of dicts
        Returns all the next possible status for a bug, in a format suitable
        for using by the options of a select widget in a form:
        [ { 'value':'STATUSVALUE', 'content':'Status Name', 'selected':<0|1> },
          ...
        ]
        """
        statusMap = []
        for status in status_transitions.get(self.status, ['NEW']):
            statusItem = {'value':status,
                          'content':self.getStatusLabel(status),
                          'selected': status == self.status}
            statusMap.append(statusItem)
        # statusMap will be [] if our status is invalid.

        # if the role is Manager we will list all status
        user = self.getAuthenticatedUser()
        next_status = [sts['value'] for sts in statusMap]
        if 'Manager' in user.getRolesInContext(self):
            for status in self.optionsForStatus():
                if status not in next_status:
                    statusItem = {'value':status,
                                  'content': '* %s' % self.getStatusLabel(status),
                                  'selected': status == self.status}
                    statusMap.append(statusItem)
        return statusMap

    security.declareProtected(AccessControl.Permissions.view, 'getSeverityOptions')
    def getSeverityOptions(self):
        severityMap = []
        for severity in self.optionsForSeverity():
            severityItem = {'value': severity,
                            'content': self.getSeverityLabel(severity),
                            'selected': severity == self.severity}
            severityMap.append(severityItem)
        return severityMap

    security.declareProtected(AccessControl.Permissions.view, 'open')
    def open(self):
        return ALL_STATUS_RESULTS[self.status] == STATUS_OPEN
        
    security.declareProtected(AccessControl.Permissions.view, 'editableProperty')
    def editableProperty(self, property_name):
        '''template helper '''
        '''return true if property should be editable by current user'''
        user = self.getAuthenticatedUser()
        roles = user.getRolesInContext(self)
        # for now, all programmer actors are managers
        actorCodes = []
        if 'Manager' in roles:
            actorCodes.append('P')
        if 'Customer' in roles:
            actorCodes.append('C')
        if not actorCodes:
            return 0
        for actorCode in actorCodes:
            if actorCode in self.properties[property_name]['editable_by']:
                return 1
        return 0
        
    security.declareProtected(AccessControl.Permissions.view, 'visibleProperty')
    def visibleProperty(self, property_name):
        '''template helper '''
        '''return true if property should be displayed to current user'''
        if getattr(self, property_name):
            return 1
        elif self.editableProperty(property_name):
            return 1
        else:
            return 0

    security.declareProtected(AccessControl.Permissions.view, 'listAttachments')
    def listAttachments(self):
        '''list attached files'''
        return self.objectValues('File')

    security.declareProtected(AccessControl.Permissions.view, 'listLog')
    def listLog(self):
        '''return log''' 
        return self.log

    security.declareProtected(AccessControl.Permissions.view, 'listComments')
    def listComments(self):
        '''list of comments'''
        return self.comments.split('||')

    # Schema used by add/edit forms

    security.declareProtected(AccessControl.Permissions.view, 'schema')
    schema = PageTemplateFile('schema.pt', _wwwDir)

    security.declareProtected(permissions.EDIT_BUG,'admin')
    admin = PageTemplateFile('bug_admin.pt', _wwwDir)    

def containerFilter(container):
    return interfaces.IBugRepository.isImplementedBy(container)

def manage_addBug(container, REQUEST):
    """Bug factory:
    Just redirect to the BugRepository constructor
    """
    REQUEST.RESPONSE.redirect(container.absolute_url() + "/manage_addBugForm")

bugConstructors=(manage_addBug,)


Globals.InitializeClass(Bug)
